#ifndef COMMON_INTERFACE_SHADER_H
#define COMMON_INTERFACE_SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtx/dual_quaternion.hpp>

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace gl {
class IShader {
public:
    IShader(const IShader& shader) = delete;

protected:
    explicit IShader(const std::string& programName_)
    {
        //创建着色器程序
        programHandle = glCreateProgram();
        programName = programName_;
    }

    ~IShader()
    {
        //删除着色器程序
        glDeleteProgram(programHandle);
    }

    inline void LoadVertexShaderSource(const char* source) { vertexSource = source; }

    inline void LoadFragmentShaderSource(const char* source) { fragmentSource = source; }

    inline bool CompileShaderAndLink()
    {
        int success;
        char info[2048];
        //创建顶点着色器与片段着色器
        uint32_t vertexShader = glCreateShader(GL_VERTEX_SHADER);
        uint32_t fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        if (vertexSource.length()) {
            //顶点着色器源码
            const char* source = vertexSource.c_str();
            //源码绑定
            glShaderSource(vertexShader, 1, &source, nullptr);
            //着色器编译
            glCompileShader(vertexShader);
            //查询编译状态
            glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
            if (!success) {
                //获取错误信息
                glGetShaderInfoLog(vertexShader, sizeof(info), nullptr, info);
                std::stringstream ss;
                ss << "Vertex shader of program " << programName << " Compile error:\n"
                   << info;
                errorInfo = ss.str();
                return false;
            }
            //将着色器附加到着色器程序上
            glAttachShader(programHandle, vertexShader);
        }
        if (fragmentSource.length()) {
            const char* source = fragmentSource.c_str();
            glShaderSource(fragmentShader, 1, &source, nullptr);
            glCompileShader(fragmentShader);
            glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
            if (!success) {
                glGetShaderInfoLog(fragmentShader, sizeof(info), nullptr, info);
                std::stringstream ss;
                ss << "Vertex shader of program " << programName << " Compile error:\n"
                   << info;
                errorInfo = ss.str();
                return false;
            }
            glAttachShader(programHandle, fragmentShader);
        }
        //连接着色器程序
        glLinkProgram(programHandle);
        //检查着色器程序连接状态
        glGetProgramiv(programHandle, GL_LINK_STATUS, &success);
        if (!success) {
            //获取错误信息
            glGetProgramInfoLog(programHandle, sizeof(info), nullptr, info);
            errorInfo = "Program " + programName + " link error:\n" + info;
            return false;
        }
        //删除没用的着色器
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        return true;
    }

    [[nodiscard]] inline const char* GetErrorInfo() const { return errorInfo.c_str(); }

    [[nodiscard]] inline uint32_t GetProgramHandle() const { return programHandle; }

    [[nodiscard]] inline const std::string& GetProgramName() const { return programName; }

private:
    uint32_t programHandle = 0;
    std::string vertexSource;
    std::string fragmentSource;
    std::string errorInfo;
    std::string programName;

public:
    inline void UniformBlockBinding(const char* name, int value)
    {
        glUniformBlockBinding(programHandle, glGetUniformBlockIndex(programHandle, name), value);
        // 打印错误信息
        if (glGetError() != GL_NO_ERROR) {
            std::stringstream ss;
            ss << "Uniform block binding error: " << name << " " << value;
            errorInfo = ss.str();
            // 直接打印
            std::cout << errorInfo << std::endl;
        }
    }

    inline void UseShader() const
    {
        //激活着色器程序
        glUseProgram(programHandle);
    }

    void setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(programHandle, name.c_str()), (int)value);
    }

    void setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(programHandle, name.c_str()), value);
    }

    void setIntArray(const std::string &name, int count, int* value) const
    {
        glUniform1iv(glGetUniformLocation(programHandle, name.c_str()), count, value);
    }

    void setUIntArray(const std::string &name, int count, uint32_t *value) const
    {
        glUniform1uiv(glGetUniformLocation(programHandle, name.c_str()), count, value);
    }

    void setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(programHandle, name.c_str()), value);
    }

    void setVec2(const std::string& name, const glm::vec2& value) const
    {
        glUniform2fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
    }

    void setVec2(const std::string& name, float x, float y) const
    {
        glUniform2f(glGetUniformLocation(programHandle, name.c_str()), x, y);
    }

    void setVec3(const std::string& name, const glm::vec3& value) const
    {
        glUniform3fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
    }

    void setVec3(const std::string& name, float x, float y, float z) const
    {
        glUniform3f(glGetUniformLocation(programHandle, name.c_str()), x, y, z);
    }

    void setVec4(const std::string& name, const glm::vec4& value) const
    {
        glUniform4fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
    }

    void setVec4(const std::string& name, float x, float y, float z, float w) const
    {
        glUniform4f(glGetUniformLocation(programHandle, name.c_str()), x, y, z, w);
    }

    void setMat2(const std::string& name, const glm::mat2& mat) const
    {
        glUniformMatrix2fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void setMat3(const std::string& name, const glm::mat3& mat) const
    {
        glUniformMatrix3fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void setMat4(const std::string& name, const glm::mat4& mat) const
    {
        glUniformMatrix4fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

    void setMat4Array(const std::string& name, int count, const glm::mat4* mat) const
    {
        glUniformMatrix4fv(glGetUniformLocation(programHandle, name.c_str()), count, GL_FALSE, &mat[0][0][0]);
    }

    void setVec3Array(const std::string& name, int count, const glm::vec3* vecs) const
    {
        glUniform3fv(glGetUniformLocation(programHandle, name.c_str()), count, &vecs[0][0]);
    }

    void setDualQuat(const std::string& name, int count, const glm::fdualquat* dual_quats) const
    {
        glUniformMatrix2x4fv(glGetUniformLocation(programHandle, name.c_str()), count, GL_FALSE, (float*)&dual_quats[0]);
    }

    std::string GetShaderLog() const
    {
        char info[2048];
        glGetProgramInfoLog(programHandle, sizeof(info), nullptr, info);
        std::stringstream ss;
        ss << "Program " << programName << " log:\n"
           << info;
        return ss.str();
    }
};
}

#endif //COMMON_INTERFACE_SHADER_H
